package com.general.framework.core.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * API 签名工具类 - 独立版本
 * 
 * 基于 PHP OpenSignControl 中间件的签名算法实现
 * 支持嵌套对象扁平化、时间戳验证和 MD5 签名生成
 * 无外部依赖，可在 IDEA 中直接运行
 * 
 * @author Generated from PHP OpenSignControl
 * @date 2025-07-26
 */
@Slf4j
public class StandaloneSignatureUtils {
    
    /**
     * 时间戳有效期（毫秒）- 5分钟
     */
    private static final long TIMESTAMP_VALIDITY_MS = 300000L;
    
    /**
     * 签名验证结果类
     */
    public static class SignatureResult {
        private boolean verified;
        private String calculatedSign;
        private String rawString;
        
        public SignatureResult(boolean verified, String calculatedSign, String rawString) {
            this.verified = verified;
            this.calculatedSign = calculatedSign;
            this.rawString = rawString;
        }
        
        // Getters
        public boolean isVerified() { return verified; }
        public String getCalculatedSign() { return calculatedSign; }
        public String getRawString() { return rawString; }
        
        @Override
        public String toString() {
            return "SignatureResult{" +
                    "verified=" + verified +
                    ", calculatedSign='" + calculatedSign + '\'' +
                    ", rawString='" + rawString + '\'' +
                    '}';
        }
    }
    
    /**
     * 签名详细信息类
     */
    public static class SignatureDetail {
        private String signature;
        private String rawString;
        
        public SignatureDetail(String signature, String rawString) {
            this.signature = signature;
            this.rawString = rawString;
        }
        
        // Getters
        public String getSignature() { return signature; }
        public String getRawString() { return rawString; }
        
        @Override
        public String toString() {
            return "SignatureDetail{" +
                    "signature='" + signature + '\'' +
                    ", rawString='" + rawString + '\'' +
                    '}';
        }
    }
    
    /**
     * 验证请求签名
     * 
     * @param data 请求数据
     * @param timestamp 时间戳
     * @param partnerSecret 合作伙伴密钥
     * @param providedSign 提供的签名
     * @return 签名验证结果
     */
    public static SignatureResult verifySignature(Map<String, Object> data, String timestamp, 
                                                 String partnerSecret, String providedSign) {
        // 获取原始字符串和计算的签名
        SignatureDetail signResult = generateSignatureWithDetail(data, timestamp, partnerSecret);
        String rawString = signResult.getRawString();
        String calculatedSign = signResult.getSignature();
        
        // 使用安全的字符串比较
        boolean verified = calculatedSign.equals(providedSign);
        
        return new SignatureResult(verified, calculatedSign, rawString);
    }
    
    /**
     * 生成签名
     * 
     * @param data 请求数据
     * @param timestamp 时间戳
     * @param partnerSecret 合作伙伴密钥
     * @return 签名字符串
     */
    public static String generateSignature(Map<String, Object> data, String timestamp, String partnerSecret) {
        SignatureDetail result = generateSignatureWithDetail(data, timestamp, partnerSecret);
        return result.getSignature();
    }
    
    /**
     * 生成签名，并返回原始字符串
     * 
     * @param data 请求数据
     * @param timestamp 时间戳
     * @param partnerSecret 合作伙伴密钥
     * @return 包含签名和原始字符串的对象
     */
    public static SignatureDetail generateSignatureWithDetail(Map<String, Object> data, String timestamp, String partnerSecret) {
        // 创建数据副本，避免修改原始数据
        Map<String, Object> dataForSign = new HashMap<>(data);
        
        // 1. 添加时间戳
        dataForSign.put("timestamp", timestamp);
        
        // 2. 扁平化嵌套对象并按键名排序
        Map<String, String> flatData = flattenAndSortData(dataForSign);
        
        // 3. 转为查询字符串格式
        String queryString = convertToQueryString(flatData);
        
        // 4. 添加密钥
        String stringToSign = queryString + "&key=" + partnerSecret;
        
        // 5. 生成MD5签名
        String signature = md5(stringToSign);
        
        return new SignatureDetail(signature, stringToSign);
    }
    
    /**
     * 扁平化嵌套数组并按键名排序
     * 
     * @param data 要处理的数据
     * @return 处理后的数据
     */
    private static Map<String, String> flattenAndSortData(Map<String, Object> data) {
        // 首先修复JSON可能导致的斜杠转义问题
        Map<String, Object> fixedData = fixJsonSlashEscaping(data);
        
        // 扁平化数据
        Map<String, String> flatData = flattenObject(fixedData, "");
        
        // 按键名排序（TreeMap自动排序）
        return new TreeMap<>(flatData);
    }
    
    /**
     * 修复JSON解析时斜杠(\/)转义问题
     * 
     * @param data 要处理的数据
     * @return 处理后的数据
     */
    private static Map<String, Object> fixJsonSlashEscaping(Map<String, Object> data) {
        Map<String, Object> result = new HashMap<>();
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            
            if (value instanceof String) {
                String strValue = (String) value;
                // 处理各种可能的斜杠转义形式
                strValue = strValue.replace("\\/", "/");
                strValue = strValue.replace("\\//", "/");
                strValue = strValue.replace("%5C%2F", "/");
                strValue = strValue.replace("%5C/", "/");
                
                result.put(key, strValue);
            } else if (value instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, Object> mapValue = (Map<String, Object>) value;
                result.put(key, fixJsonSlashEscaping(mapValue));
            } else {
                result.put(key, value);
            }
        }
        
        return result;
    }
    
    /**
     * 将对象扁平化为一级对象
     * 
     * @param data 要扁平化的数据
     * @param prefix 前缀
     * @return 扁平化后的数据
     */
    private static Map<String, String> flattenObject(Map<String, Object> data, String prefix) {
        Map<String, String> result = new HashMap<>();
        
        for (Map.Entry<String, Object> entry : data.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            String newKey = prefix.isEmpty() ? key : prefix + "." + key;
            
            if (value instanceof Map && !isIndexedArray(value)) {
                // 处理关联数组/对象
                @SuppressWarnings("unchecked")
                Map<String, Object> mapValue = (Map<String, Object>) value;
                result.putAll(flattenObject(mapValue, newKey));
            } else if (value instanceof List || (value instanceof Map && isIndexedArray(value))) {
                // 处理索引数组 - 简化的JSON序列化
                result.put(newKey, simpleJsonEncode(value));
            } else {
                // 简单值
                result.put(newKey, value != null ? value.toString() : "");
            }
        }
        
        return result;
    }
    
    /**
     * 简化的JSON编码（不依赖外部库）
     * 
     * @param obj 要编码的对象
     * @return JSON字符串
     */
    private static String simpleJsonEncode(Object obj) {
        if (obj == null) {
            return "null";
        }
        
        if (obj instanceof String) {
            return "\"" + escapeJsonString((String) obj) + "\"";
        }
        
        if (obj instanceof Number || obj instanceof Boolean) {
            return obj.toString();
        }
        
        if (obj instanceof List) {
            List<?> list = (List<?>) obj;
            StringBuilder sb = new StringBuilder("[");
            for (int i = 0; i < list.size(); i++) {
                if (i > 0) sb.append(",");
                sb.append(simpleJsonEncode(list.get(i)));
            }
            sb.append("]");
            return sb.toString();
        }
        
        if (obj instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> map = (Map<String, Object>) obj;
            StringBuilder sb = new StringBuilder("{");
            boolean first = true;
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                if (!first) sb.append(",");
                sb.append("\"").append(escapeJsonString(entry.getKey())).append("\":");
                sb.append(simpleJsonEncode(entry.getValue()));
                first = false;
            }
            sb.append("}");
            return sb.toString();
        }
        
        return "\"" + escapeJsonString(obj.toString()) + "\"";
    }
    
    /**
     * 转义JSON字符串中的特殊字符
     * 
     * @param str 要转义的字符串
     * @return 转义后的字符串
     */
    private static String escapeJsonString(String str) {
        return str.replace("\\", "\\\\")
                  .replace("\"", "\\\"")
                  .replace("\b", "\\b")
                  .replace("\f", "\\f")
                  .replace("\n", "\\n")
                  .replace("\r", "\\r")
                  .replace("\t", "\\t");
    }
    
    /**
     * 检查对象是否为索引数组（简化版本）
     * 
     * @param obj 要检查的对象
     * @return 是否为索引数组
     */
    private static boolean isIndexedArray(Object obj) {
        if (!(obj instanceof Map)) {
            return false;
        }
        
        @SuppressWarnings("unchecked")
        Map<String, Object> map = (Map<String, Object>) obj;
        
        // 检查键是否都是数字且连续
        Set<String> keys = map.keySet();
        try {
            List<Integer> numKeys = keys.stream()
                    .map(Integer::parseInt)
                    .sorted()
                    .collect(Collectors.toList());
            
            for (int i = 0; i < numKeys.size(); i++) {
                if (numKeys.get(i) != i) {
                    return false;
                }
            }
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
    
    /**
     * 将数组转换为URL查询字符串格式
     * 
     * @param data 要转换的数据
     * @return 查询字符串
     */
    private static String convertToQueryString(Map<String, String> data) {
        List<String> pairs = new ArrayList<>();
        
        for (Map.Entry<String, String> entry : data.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            
            // 使用自定义的encodeURIComponent方法
            String encodedKey = encodeURIComponent(key);
            String encodedValue = encodeURIComponent(value);
            
            pairs.add(encodedKey + "=" + encodedValue);
        }
        
        return String.join("&", pairs);
    }
    
    /**
     * 模拟JavaScript的encodeURIComponent函数
     * 与URLEncoder的主要区别是空格编码为%20而非+
     * 
     * @param str 要编码的字符串
     * @return 编码后的字符串
     */
    private static String encodeURIComponent(String str) {
        if (str == null) {
            return "";
        }
        
        // 首先处理转义的斜杠
        str = str.replace("\\/", "/");
        str = str.replace("\\//", "/");
        str = str.replace("%5C%2F", "/");
        str = str.replace("%5C/", "/");
        
        try {
            // 先使用URLEncoder进行基本编码
            String result = URLEncoder.encode(str, "UTF-8");
            
            // 将+替换为%20，以与JavaScript的encodeURIComponent一致
            result = result.replace("+", "%20");
            
            // 根据JavaScript实现添加更多替换
            // encodeURIComponent不会编码：! ' ( ) * ~
            result = result.replace("%21", "!");
            result = result.replace("%27", "'");
            result = result.replace("%28", "(");
            result = result.replace("%29", ")");
            result = result.replace("%2A", "*");
            result = result.replace("%7E", "~");
            
            return result;
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8 encoding not supported", e);
        }
    }
    
    /**
     * 验证时间戳是否在有效期内
     * 
     * @param timestamp 时间戳（毫秒）
     * @return 是否有效
     */
    public static boolean isTimestampValid(String timestamp) {
        try {
            long timestampLong = Long.parseLong(timestamp);
            long currentTime = System.currentTimeMillis();
            return Math.abs(currentTime - timestampLong) < TIMESTAMP_VALIDITY_MS;
        } catch (NumberFormatException e) {
            return false;
        }
    }
    
    /**
     * 生成MD5哈希
     * 
     * @param input 输入字符串
     * @return MD5哈希值
     */
    private static String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hashBytes = md.digest(input.getBytes("UTF-8"));
            
            StringBuilder sb = new StringBuilder();
            for (byte b : hashBytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            throw new RuntimeException("MD5 hashing failed", e);
        }
    }
    
    // ==================== 测试和示例代码 ====================
    
    /**
     * 主方法 - 运行测试示例
     */
    public static void main(String[] args) {
        System.out.println("=== Partner API 签名工具测试 ===\n");
        
        // 示例1: 简单参数签名
        testSimpleSignature();
        
        // 示例2: 复杂嵌套对象签名
        testComplexSignature();
        
        // 示例3: 签名验证
        testSignatureVerification();
        
        // 示例4: 时间戳验证
        testTimestampValidation();
        
        // 示例5: 品牌列表 API 签名示例
        testBrandListApi();
    }
    
    /**
     * 测试简单参数签名
     */
    private static void testSimpleSignature() {
        System.out.println("📋 测试1: 简单参数签名");
        System.out.println("========================");
        
        // 准备请求参数
        Map<String, Object> data = new HashMap<>();
//        data.put("partnerId", "100106");
        data.put("categoryId", "100001");
        data.put("size", "1");
        
        // 时间戳和密钥
        String timestamp = String.valueOf(System.currentTimeMillis());
        String partnerSecret = "E8ED3E2BB7A4092D";
        
        // 生成签名
        SignatureDetail signatureDetail = generateSignatureWithDetail(data, timestamp, partnerSecret);
        
        System.out.println("请求参数: " + data);
        System.out.println("时间戳: " + timestamp);
        System.out.println("原始字符串: " + signatureDetail.getRawString());
        System.out.println("生成签名: " + signatureDetail.getSignature());
        System.out.println();
    }
    
    /**
     * 测试复杂嵌套对象签名
     */
    private static void testComplexSignature() {
        System.out.println("📋 测试2: 复杂嵌套对象签名");
        System.out.println("============================");
        
        // 准备复杂的嵌套参数
        Map<String, Object> data = new HashMap<>();
        
        // 嵌套的地址信息
        Map<String, Object> addressInfo = new HashMap<>();
        addressInfo.put("districtCode", "110101");
        addressInfo.put("userName", "张三");
        addressInfo.put("userPhone", "13800138000");
        addressInfo.put("address", "北京市朝阳区某某街道某某小区");
        data.put("addressInfo", addressInfo);
        
        data.put("evaluateId", "1949113613439021056");
        data.put("appointStartTime", "2025-07-26 10:00:00");
        data.put("appointEndTime", "2025-07-26 12:00:00");
        data.put("channel", "线上");
        data.put("remark", "无");
        data.put("notifyUrl", "https://example.com/notify");
        
        // 时间戳和密钥
        String timestamp = String.valueOf(System.currentTimeMillis());
        String partnerSecret = "kLJJLIDaCt7dfPNWL6+";
        
        // 生成签名
        SignatureDetail signatureDetail = generateSignatureWithDetail(data, timestamp, partnerSecret);
        
        System.out.println("请求参数: " + data);
        System.out.println("时间戳: " + timestamp);
        System.out.println("原始字符串: " + signatureDetail.getRawString());
        System.out.println("生成签名: " + signatureDetail.getSignature());
        System.out.println();
    }
    
    /**
     * 测试签名验证
     */
    private static void testSignatureVerification() {
        System.out.println("📋 测试3: 签名验证");
        System.out.println("==================");
        
        // 准备请求参数
        Map<String, Object> data = new HashMap<>();
        data.put("partnerId", "test_partner_001");
        data.put("productId", "100001");
        data.put("action", "query");
        
        String timestamp = String.valueOf(System.currentTimeMillis());
        String partnerSecret = "kLJJLIDaCt7dfPNWL6+";
        
        // 生成正确的签名
        String correctSignature = generateSignature(data, timestamp, partnerSecret);
        
        // 验证正确的签名
        SignatureResult result1 = verifySignature(data, timestamp, partnerSecret, correctSignature);
        System.out.println("✅ 正确签名验证: " + (result1.isVerified() ? "通过" : "失败"));
        System.out.println("   提供的签名: " + correctSignature);
        System.out.println("   计算的签名: " + result1.getCalculatedSign());
        
        // 验证错误的签名
        String wrongSignature = "wrong_signature_12345";
        SignatureResult result2 = verifySignature(data, timestamp, partnerSecret, wrongSignature);
        System.out.println("❌ 错误签名验证: " + (result2.isVerified() ? "通过" : "失败"));
        System.out.println("   提供的签名: " + wrongSignature);
        System.out.println("   计算的签名: " + result2.getCalculatedSign());
        System.out.println();
    }
    
    /**
     * 测试时间戳验证
     */
    private static void testTimestampValidation() {
        System.out.println("📋 测试4: 时间戳验证");
        System.out.println("====================");
        
        // 当前时间戳（有效）
        String currentTimestamp = String.valueOf(System.currentTimeMillis());
        boolean isCurrentValid = isTimestampValid(currentTimestamp);
        System.out.println("✅ 当前时间戳: " + currentTimestamp + " -> " + (isCurrentValid ? "有效" : "无效"));
        
        // 过期时间戳（无效）
        String expiredTimestamp = String.valueOf(System.currentTimeMillis() - 600000); // 10分钟前
        boolean isExpiredValid = isTimestampValid(expiredTimestamp);
        System.out.println("❌ 过期时间戳: " + expiredTimestamp + " -> " + (isExpiredValid ? "有效" : "无效"));
        
        // 未来时间戳（无效）
        String futureTimestamp = String.valueOf(System.currentTimeMillis() + 600000); // 10分钟后
        boolean isFutureValid = isTimestampValid(futureTimestamp);
        System.out.println("❌ 未来时间戳: " + futureTimestamp + " -> " + (isFutureValid ? "有效" : "无效"));
        System.out.println();
    }
    
    /**
     * 测试品牌列表 API 签名示例（模拟真实场景）
     */
    private static void testBrandListApi() {
        System.out.println("📋 测试5: 品牌列表 API 签名示例");
        System.out.println("================================");
        
        // 模拟品牌列表 API 请求参数
        Map<String, Object> requestData = new HashMap<>();
        requestData.put("categoryId", "100001");
        requestData.put("size", "10");

        String timestamp = String.valueOf(System.currentTimeMillis());
        String partnerSecret = "kLJJLIDaCt7dfPNWL6+";
        
        // 生成签名详情
        SignatureDetail signatureDetail = generateSignatureWithDetail(requestData, timestamp, partnerSecret);
        
        System.out.println("🔗 API 端点: GET /open/brand/list");
        System.out.println("📝 请求参数:");
        requestData.forEach((key, value) -> System.out.println("   " + key + ": " + value));
        
        System.out.println("\n🔐 签名信息:");
        System.out.println("   时间戳: " + timestamp);
        System.out.println("   原始字符串: " + signatureDetail.getRawString());
        System.out.println("   MD5签名: " + signatureDetail.getSignature());
        
        System.out.println("\n📡 HTTP 请求头:");
        System.out.println("   Content-Type: application/json");
        System.out.println("   sign: " + signatureDetail.getSignature());
        System.out.println("   timestamp: " + timestamp);
        System.out.println("   partnerId: partner_huishou_001");
        
        // 验证生成的签名
        SignatureResult verification = verifySignature(
            requestData, timestamp, partnerSecret, signatureDetail.getSignature()
        );
        System.out.println("\n✅ 签名验证: " + (verification.isVerified() ? "通过" : "失败"));
        System.out.println();
    }
}
